package cz.farin.mandelinka;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import cz.farin.mandelinka.gui.BugWindow;
import cz.farin.mandelinka.gui.UserInterface;


public class Simulation {
	
	public static final int SCORE_KILL = 2;
	public static final int SCORE_SURVIVE = 1;
	public static final int SCORE_EXCEPTION_PENALTY = -3;
	
	//configuration properties
	private Maze maze;
	private int timeLimit;	
	private int stepInterval; //miliseconds
	private boolean killMode;
	
	//other fields
	private List<BugHandle> bugs;
	private List<BugHandle> aliveBugs;
	
	private static final int RANDOM_POSITION = -1;
	
	private UserInterface ui;
	
	public UserInterface getUserInterface() {
		return ui;
	}

	public void setUserInterface(UserInterface ui) {
		this.ui = ui;
	}

	public Simulation() {
		//maze = Maze.createEmpty(10, 10);
		bugs = new ArrayList<BugHandle>();
		aliveBugs = new ArrayList<BugHandle>();
		timeLimit = 150;
		stepInterval = 150;
		killMode = true;
	}
	
	public void addBug(Bug bug) {
		//TODO init random position here 
		addBug(bug, RANDOM_POSITION, RANDOM_POSITION, null);
	}
	
	public void addBug(Bug bug, int x, int y) {
		addBug(bug, x, y, null);
	}
	
	public void addBug(Bug bug, int x, int y, Direction rotation) {		
		if (maze == null) {
			throw new IllegalStateException("Maze must be created before bugs are placed into.");
		}		
		if (x != RANDOM_POSITION && y != RANDOM_POSITION && ! isEmptyTile(x,y)) {
			throw new IllegalArgumentException("Cannot place bug on tile ["+x+", "+y + "]");
		}
		BugHandle bh = new BugHandle(bug,aliveBugs.size() + 1);  
		bh.setX(x);
		bh.setY(y);
		bh.setRotation(rotation);
		bugs.add(bh);		
		aliveBugs.add(bh);		
	}
	
	private boolean isEmptyTile(int x, int y) {
		if (! maze.get(x, y).canWalk()) {
			return false;
		}
		for (BugHandle bh: aliveBugs) {
			if (bh.getX() == x && bh.getY() == y) {
				return false;
			}
		}
		return true;		
	}
	
	public void simulate() {		
		if (ui == null) {
			//use default UI
			ui = new BugWindow();
			((BugWindow)ui).init();
		}		
		//TODO handle in add bug
		placeBugsIntoMaze();
		
		ui.simulationStart(this);
		
		for(int time = 0; time < timeLimit; time++) {
			simulateStep();
		}
		
		for(BugHandle bh: aliveBugs) {
			bh.increaseScore(SCORE_SURVIVE);
		}
		
		ui.simulationOver();
	}
	
	private void simulateStep() {
		for(BugHandle bh: aliveBugs) {
			maze.fillSurroundings(bh.getX(), bh.getY(), bh.getSurroundings());				
		}			
		//TODO fill other bugInfo
		for(BugHandle bh: aliveBugs) {				 
			//own bugInfo
			bh.getSurroundings().getCenterSquare().setBugInfo(bh.toBugInfo());
		}
		
		//1. request moves and proceed turns 
		for(BugHandle bh: aliveBugs) {	
			Movement move = bh.requestMove();
			//turns has highest priority
			if (move.isTurn()) {
				bh.doMove();
			}
		}
		//2 resolve illegal moves (walls)
		for(BugHandle bh: aliveBugs) {	
			if (! maze.get(bh.getRequestedPosition()).canWalk()) {
				bh.changeIllegalMove();
			}
		}			
		//3. resolve conflicts
		Map<Position,BugHandle> moveTargets = new HashMap<Position, BugHandle>();
		Set<BugHandle> killed = new HashSet<BugHandle>();
		boolean noConflict = false;
		outer:
		while(! noConflict) {
			moveTargets.clear();
			killed.clear();
			//3a. non-conflict moves
			for(BugHandle bh: aliveBugs) {
				if (! bh.getRequestedMove().isPositionChange()) {
					moveTargets.put(bh.getRequestedPosition(),bh);
				}										
			}
			//3b. position change moves
			for(BugHandle bh: aliveBugs) {
				if (bh.getRequestedMove().isPositionChange()) {
					Position myRequestedPos = bh.getRequestedPosition();
					
					//first deny "swap" move A><B
					Position actual = bh.getActualPosition();
					if (moveTargets.containsKey(actual)) {				
						BugHandle bugToMyPos = moveTargets.get(actual);
						if (myRequestedPos.equals(bugToMyPos.getActualPosition())) {
							//bug on my target has my position as target 
							bugToMyPos.changeIllegalMove();
							bh.changeIllegalMove();
							//need to put positionChange movement into map ???? check								
							continue outer;	
						}
					}																			
					if (moveTargets.containsKey(myRequestedPos)) {
						BugHandle conflict = moveTargets.get(myRequestedPos);
						if (! killMode ||							
							conflict.getRequestedMove().isPositionChange() ||
							bh.getRotation() == conflict.getRotation().rotate180()) {
							
							conflict.changeIllegalMove();
							bh.changeIllegalMove();
							//need to put positionChange movement into map ??? check						
							continue outer;																
						} 
						killed.add(conflict);
						conflict.setAlive(false);
						bh.increaseScore(SCORE_KILL);
						//success kill - no conflict
						//position change movement must be rather in moveTargets map (so "moveTargets.put(p, bh);" needed)
					} 
					moveTargets.put(myRequestedPos, bh);
				}
			}
			noConflict = true;
		}
		moveTargets.clear();
		
		//5. remvove killed bugs
		aliveBugs.removeAll(killed);
		killed.clear();
		
		//4. proceed forward and backward moves
		for(BugHandle bh: aliveBugs) {	
			Movement move = bh.getRequestedMove();
			if (move.isPositionChange()) {
				bh.doMove();
			}
		}
		
		
		ui.stepOver();
		
		try {
			Thread.sleep(stepInterval);
		} catch (InterruptedException e) {
			//empty
		}
	}

	private void placeBugsIntoMaze() {
		Random random = new Random();
		
		HashSet<Position> emptyPositions = new HashSet<Position>();
		for(int x = 0; x < maze.getSizeX(); x++) {
			for(int y = 0; y < maze.getSizeY(); y++) {
				if (maze.get(x, y) == SquareType.PASSAGE) {
					emptyPositions.add(new Position(x,y));
				}
			}
		}
		
		for(BugHandle bh: bugs) {				 
			if (bh.getRotation() == null) {
				int r = random.nextInt(4);
				Direction d = Direction.values()[r * 2];				
				bh.setRotation(d);
			}
			int x = bh.getX();
			int y = bh.getY();
			if (x != RANDOM_POSITION && y != RANDOM_POSITION) {				
				removeNeigbours(emptyPositions, x, y);
			}
		}
		
		for(BugHandle bh: bugs) {
			if (bh.getX() == RANDOM_POSITION || bh.getY() == RANDOM_POSITION) {
				if (emptyPositions.size() == 0) {
					throw new IllegalStateException("No empty tile to place bug in maze. (Or all empty places are next to another bugs)");
				}
				int idx = random.nextInt(emptyPositions.size());
				Position pos = (Position) emptyPositions.toArray()[idx];
				bh.setX(pos.x);
				bh.setY(pos.x);
				removeNeigbours(emptyPositions, pos.x, pos.y);
			}			
		}		
	}
	
	private void removeNeigbours(HashSet<Position> emptyPositions, int x, int y) {
		emptyPositions.remove(new Position(x,y));
		emptyPositions.remove(new Position(x+1,y));
		emptyPositions.remove(new Position(x-1,y));
		emptyPositions.remove(new Position(x,y+1));
		emptyPositions.remove(new Position(x,y-1));
	}

	public Maze getMaze() {
		return maze;
	}
	
	public void setMaze(Maze maze) {
		this.maze = maze;
	}
	
	public List<BugHandle> getAllBugs() {
		return bugs;
	}
	
	public List<BugHandle> getAliveBugs() {
		return aliveBugs;
	}

	public boolean isKillMode() {
		return killMode;
	}

	public void setKillMode(boolean killMode) {
		this.killMode = killMode;
	}

	public int getTimeLimit() {
		return timeLimit;
	}

	public void setTimeLimit(int timeLimit) {
		this.timeLimit = timeLimit;
	}


}
